// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
using System.Diagnostics.CodeAnalysis;
using WasmAppBuilder;

using JoinedString;
//
// This class generates the g_wasmThunks array and CallFunc_* functions used by the CoreCLR interpreter to call native code on WASM.
// The generated code should be kept in sync with the corresponding CoreCLR runtime code that consumes these thunks and call functions.
//

#nullable enable

internal sealed class InterpToNativeGenerator
{
    private LogAdapter Log { get; set; }

    public InterpToNativeGenerator(LogAdapter log) => Log = log;

    public void Generate(IEnumerable<string> cookies, string outputPath)
    {
        using TempFileName tmpFileName = new();
        using (var w = File.CreateText(tmpFileName.Path))
        {
            Emit(w, cookies);
        }

        if (Utils.CopyIfDifferent(tmpFileName.Path, outputPath, useHash: false))
            Log.LogMessage(MessageImportance.Low, $"Generating managed2native table to '{outputPath}'.");
        else
            Log.LogMessage(MessageImportance.Low, $"Managed2native table in {outputPath} is unchanged.");
    }

    private static string SignatureToArguments(string signature)
    {
        if (signature.Length <= 1)
            return "void";

        return string.Join(", ", signature.Skip(1).Select(static c => SignatureMapper.CharToNativeType(c)));
    }

    private static string CallFuncName(IEnumerable<char> args, string result)
    {
        var paramTypes = args.Any() ? args.Join("_", (p, i) => SignatureMapper.CharToNameType(p)).ToString() : "Void";

        return $"CallFunc_{paramTypes}_Ret{result}";
    }

    private static void Emit(StreamWriter w, IEnumerable<string> cookies)
    {
        // Use OrderBy because Order() is not available on .NET Framework
        var signatures = cookies.OrderBy(c => c).Distinct().ToArray();
        Array.Sort(signatures, StringComparer.Ordinal);

        w.Write(
        """
        // Licensed to the .NET Foundation under one or more agreements.
        // The .NET Foundation licenses this file to you under the MIT license.
        //

        //
        // GENERATED FILE, DON'T EDIT
        // Generated by coreclr InterpToNativeGenerator
        //

        #include <callhelpers.hpp>

        // Arguments are passed on the stack with each argument aligned to INTERP_STACK_SLOT_SIZE.
        #define ARG_ADDR(i) (pArgs + (i * INTERP_STACK_SLOT_SIZE))
        #define ARG_IND(i) ((int32_t)((int32_t*)ARG_ADDR(i)))
        #define ARG_I32(i) (*(int32_t*)ARG_ADDR(i))
        #define ARG_I64(i) (*(int64_t*)ARG_ADDR(i))
        #define ARG_F32(i) (*(float*)ARG_ADDR(i))
        #define ARG_F64(i) (*(double*)ARG_ADDR(i))

        namespace
        {
        """);

        foreach (var signature in signatures)
        {
            try
            {
                var result = Result(signature);
                var args = Args(signature);
                var portabilityAssert = signature[0] == 'n' ? "PORTABILITY_ASSERT(\"Indirect struct return is not yet implemented.\");\n        " : "";
                w.Write(
                    $$"""

                        static void {{CallFuncName(args, SignatureMapper.CharToNameType(signature[0]))}}(PCODE pcode, int8_t* pArgs, int8_t* pRet)
                        {
                            {{result.nativeType}} (*fptr)({{args.Join(", ", (p, i) => SignatureMapper.CharToNativeType(p))}}) = ({{result.nativeType}} (*)({{args.Join(", ", (p, i) => SignatureMapper.CharToNativeType(p))}}))pcode;
                            {{portabilityAssert}}{{(result.isVoid ? "" : "*" + "((" + result.nativeType + "*)pRet) = ")}}(*fptr)({{args.Join(", ", (p, i) => $"{SignatureMapper.CharToArgType(p)}({i})")}});
                        }

                    """);
            }
            catch (InvalidSignatureCharException e)
            {
                throw new LogAsErrorException($"Element '{e.Char}' of signature '{signature}' can't be handled by managed2native generator");
            }
        }

        w.Write(
            $$"""
            }

            const StringToWasmSigThunk g_wasmThunks[] = {
            {{signatures.Join($",{w.NewLine}", signature =>
            $"    {{ \"{signature}\", (void*)&{CallFuncName(Args(signature), SignatureMapper.CharToNameType(signature[0]))} }}")}}
            };

            const size_t g_wasmThunksCount = sizeof(g_wasmThunks) / sizeof(g_wasmThunks[0]);

            """);

        static IEnumerable<char> Args(string signature)
        {
            for (int i = 1; i < signature.Length; ++i)
                yield return signature[i];
        }

        static (bool isVoid, string nativeType) Result(string signature)
            => new(SignatureMapper.IsVoidSignature(signature), SignatureMapper.CharToNativeType(signature[0]));
    }
}
